手写 ButterKnife BindView

先建三个module,分别为Butterknife ButterKnife-Annotions ButterKnife-compiler,其中butterknifeAndroid Module其余的都是Java Module

ButterKnife-Annotions:提供注解。
ButterKnife-compiler: 依赖ButterKnife-Annotions,生成代码的module
Butterknife:应用直接使用的库。

Butterknife-Annotions添加注解

1
2
3
4
5
@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
int value();
}

ButterKnife-compiler添加依赖

1
2
3
4
5
6
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.auto.service:auto-service:1.0-rc3'
implementation 'com.squareup:javapoet:1.9.0'
implementation project(':ButterKnife-Annotions')
}

写一个生成代码的类ButterKnifeProcessor继承AbstractProcessor

1
2
3
4
5
6
public class ButterKnifeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}

我们的步骤是:

  1. 添加注解@AutoService(Processor.class)
  2. 确定好我们需要生成代码的JAVA版本
  3. 确定生成代码
  4. 生成代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import com.zzw.butterknife_annotions.BindView;

import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;

//1. 添加注解 @AutoService(Processor.class)
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {

//2. 指定处理的版本 >1.7

@Override
public SourceVersion getSupportedSourceVersion() {
//最高版本
return SourceVersion.latestSupported();
}

//3. 指定处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}

//4. 在这执行生成类
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}


private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindView.class);
return annotations;
}
}

我们确定一下这个环境编译是否是正确的
Butterknife里面添加ButterKnife-compiler依赖

1
api project(':ButterKnife-compiler')

app添加Butterknife依赖和compiler生成代码依赖

1
2
implementation project(':Butterknife')
annotationProcessor project(':ButterKnife-compiler')

appMainActivity添加注解

1
2
3

@BindView(R.id.text_view)
TextView textView;

这个时候运行程序报错了提示

1
2
3
4
5
6
7
8
9
10
11
C:\Users\Swartz\Desktop\githubdemo\AptDemo\ButterKnife-compiler\src\main\java\com\zzw\butterknife_compiler\ButterKnifeProcessor.java
Error:(21, 20) 错误: 编码GBK的不可映射字符
Error:(25, 12) 错误: 编码GBK的不可映射字符
Error:(25, 17) 错误: 编码GBK的不可映射字符
Error:(29, 20) 错误: 编码GBK的不可映射字符
Error:(39, 20) 错误: 编码GBK的不可映射字符
Error:Execution failed for task ':Butterknife:javaPreCompileDebug'.
> Annotation processors must be explicitly declared now. The following dependencies on the compile classpath are found to contain annotation processor. Please add them to the annotationProcessor configuration.
- ButterKnife-compiler.jar (project :ButterKnife-compiler)
Alternatively, set android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true to continue with previous behavior. Note that this option is deprecated and will be removed in the future.
See https://developer.android.com/r/tools/annotation-processor-error-message.html for more details.

这个是因为我们在ButterKnifeProcessor这个类使用了中文,所以我们在ButterKnife-compiler中指定编码集

1
2
3
tasks.withType(JavaCompile){
options.encoding='UTF-8'
}

这个时候我们在ButterKnifeProcessor里面process函数添加打印

1
System.out.print("11111");

然后随便修改一下MainActivity的代码,重新编译在Gradle Console里面就可以看到打印了。这里需要修改MainActivity代码是因为我们是编译时生成,as为了节省时间是重新编译修改了的类,如果不修改的话可以能会不编译。

环境ok了,这下我们就需要重点放在生成代码这块了,这块又分为以下步骤:

  1. 因为这getSupportedAnnotationTypes添加的所有注解都会走到process函数,所以我们要将注解按照类来分离。比如AActivity对应那些有那些注解,BActivity里面有那些注解。
  2. 生成代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

package com.zzw.butterknife_compiler;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import com.zzw.butterknife_annotions.BindView;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;


//1. 添加注解 @AutoService(Processor.class)
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
//决定生成的文件在哪个文件夹
private Filer mFiler;
//决定生成的生成包名用
private Elements mElementUtils;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mElementUtils = processingEnv.getElementUtils();
}


//2. 指定处理的版本 >1.7

@Override
public SourceVersion getSupportedSourceVersion() {
//最高版本
return SourceVersion.latestSupported();
}

//3. 指定处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}

//4. 在这执行生成类
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//4.1 注解分类
//拿到所有的注解标记的元素
Set<? extends Element> elements= roundEnv.getElementsAnnotatedWith(BindView.class);
//分类集合
Map<Element , List<Element>> elementMap= new LinkedHashMap<>();
//进行分类
for (Element element : elements) {
//拿到属于哪个类
Element enclosingElement = element.getEnclosingElement();
List<Element> viewBindElements = elementMap.get(enclosingElement);
if(viewBindElements ==null){
viewBindElements = new ArrayList<>();
elementMap.put(enclosingElement,viewBindElements);
}
viewBindElements.add(element);
}
//4.2 生成代码
for (Map.Entry<Element,List<Element>> entry : elementMap.entrySet()) {
Element enclosingElement = entry.getKey();
List<Element> viewBindElements = entry.getValue();

//类信息
// public final class xxxActivity_ViewBinding implements Unbinder
final String activityNameStr = enclosingElement.getSimpleName().toString();
ClassName activityClassName = ClassName.bestGuess(activityNameStr);
TypeSpec.Builder classBuilder = TypeSpec.classBuilder( activityNameStr +"_ViewBinding")
.addModifiers(Modifier.FINAL,Modifier.PUBLIC)
//添加实现
.addSuperinterface(ClassName.get("com.zzw.butterknife","Unbinder"))
//添加属性
.addField(activityClassName,"target",Modifier.PRIVATE);

//方法信息
//实现unbinder方法
MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC);

unbindMethodBuilder.addStatement("$T target = this.target",activityClassName);
unbindMethodBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");");

//构造函数
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(activityClassName,"target");
constructorMethodBuilder.addStatement("this.target = target");


//findViewById
for (Element viewBindElement : viewBindElements) {
//target.textView1 = Utils.findViewById(target,R.id.text_view1);
String filedName = viewBindElement.getSimpleName().toString();
ClassName utilsClassName = ClassName.get("com.zzw.butterknife","Utils");
int resId = viewBindElement.getAnnotation(BindView.class).value();
constructorMethodBuilder.addStatement("target.$L = $T.findViewById(target,$L)",filedName,utilsClassName,resId);

unbindMethodBuilder.addStatement("target.$L = null" ,filedName);
}


//添加构造函数到类里面
classBuilder.addMethod(constructorMethodBuilder.build());
//添加unbinder方法到类里面
classBuilder.addMethod(unbindMethodBuilder.build());

try {
//拿到包名
String packageName =mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
JavaFile.builder(packageName,classBuilder.build())
.addFileComment("butterknife 自动生成")//注释
.build()
.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
System.out.print("异常");
}
}
return false;
}


private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindView.class);
return annotations;
}
}

生成的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// butterknife 自动生成
package com.zzw.app;

import com.zzw.butterknife.Unbinder;
import com.zzw.butterknife.Utils;
import java.lang.Override;

public final class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;

public MainActivity_ViewBinding(MainActivity target) {
this.target = target;
target.textView1 = Utils.findViewById(target,2131165301);
target.textView2 = Utils.findViewById(target,2131165302);
}

@Override
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");;
target.textView1 = null;
target.textView2 = null;
}
}

我们可以自动生成代码了,这个时候我们可以通过这个来实现findViewById
我们在Butterknife module里面建Butterknife这个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.zzw.butterknife;

import android.app.Activity;

import java.lang.reflect.Constructor;

/**
* Des:
* Created by zzw on 2018/1/29.
*/

public class Butterknife {

public static Unbinder bind(Activity activity){
//xxxActivity_ViewBinding viewBinding = new xxxActivity_ViewBinding(activity);
try{
Class<? extends Unbinder> bindClass= (Class<? extends Unbinder>) Class.forName(activity.getClass().getName()+"_ViewBinding");
Constructor<? extends Unbinder> bindConstructor= bindClass.getDeclaredConstructor(activity.getClass());
return bindConstructor.newInstance(activity);
}catch (Exception e){
e.printStackTrace();
return Unbinder.EMPTY;
}
}
}

MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.zzw.app;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

import com.zzw.aptdemo.R;
import com.zzw.butterknife.Butterknife;
import com.zzw.butterknife.Unbinder;
import com.zzw.butterknife_annotions.BindView;

public class MainActivity extends AppCompatActivity {


@BindView(R.id.text_view1)
TextView textView1;
@BindView(R.id.text_view2)
TextView textView2;

private Unbinder mUnbinder;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUnbinder = Butterknife.bind(this);

textView1.setText("你好 大哥");
}

@Override
protected void onDestroy() {
super.onDestroy();
mUnbinder.unbind();
}
}

该文中Butterknife module用到的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import android.support.annotation.UiThread;

/**
* Des:
* Created by zzw on 2018/1/29.
*/

public interface Unbinder {
@UiThread
void unbind();

Unbinder EMPTY = new Unbinder() {
@Override public void unbind() { }
};
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.zzw.butterknife;

import android.app.Activity;
import android.view.View;

/**
* Des:
* Created by zzw on 2018/1/29.
*/

public class Utils {
public static <T extends View>T findViewById(Activity activity,int viewId){
return activity.findViewById(viewId);
}
}
-------------The End-------------